package com.hqd.ch03.v49.beans.factory.support;

import com.hqd.ch03.v49.beans.factory.BeanDefinitionStoreException;
import com.hqd.ch03.v49.core.env.Environment;
import com.hqd.ch03.v49.core.env.EnvironmentCapable;
import com.hqd.ch03.v49.core.env.StandardEnvironment;
import com.hqd.ch03.v49.factory.support.BeanNameGenerator;
import com.hqd.ch03.v49.io.Resource;
import com.hqd.ch03.v49.io.ResourceLoader;
import com.hqd.ch03.v49.io.support.PathMatchingResourcePatternResolver;
import com.hqd.ch03.v49.io.support.ResourcePatternResolver;
import com.hqd.ch03.v49.reader.BeanDefinitionReader;
import com.hqd.ch03.v49.registry.BeanDefinitionRegistry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.IOException;
import java.util.Collections;
import java.util.Set;

public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader, EnvironmentCapable {

    /**
     * Logger available to subclasses.
     */
    protected final Log logger = LogFactory.getLog(getClass());

    private final BeanDefinitionRegistry registry;


    private ResourceLoader resourceLoader;


    private ClassLoader beanClassLoader;

    private Environment environment;

    private BeanNameGenerator beanNameGenerator = DefaultBeanNameGenerator.INSTANCE;

    public AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
        this.registry = registry;

        // Determine ResourceLoader to use.
        if (this.registry instanceof ResourceLoader) {
            this.resourceLoader = (ResourceLoader) this.registry;
        } else {
            this.resourceLoader = new PathMatchingResourcePatternResolver();
        }

        // Inherit Environment if possible
        if (this.registry instanceof EnvironmentCapable) {
            this.environment = ((EnvironmentCapable) this.registry).getEnvironment();
        } else {
            this.environment = new StandardEnvironment();
        }
    }


    /**
     * Return the bean factory to register the bean definitions with.
     * <p>The factory is exposed through the BeanDefinitionRegistry interface,
     * encapsulating the methods that are relevant for bean definition handling.
     *
     * @deprecated as of Spring Framework 5.3.15 in favor of {@link #getRegistry()},
     * to be removed in Spring Framework 6.0
     */
    @Deprecated
    public final BeanDefinitionRegistry getBeanFactory() {
        return this.registry;
    }

    @Override
    public final BeanDefinitionRegistry getRegistry() {
        return this.registry;
    }

    @Override

    public ResourceLoader getResourceLoader() {
        return this.resourceLoader;
    }

    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    @Override

    public ClassLoader getBeanClassLoader() {
        return this.beanClassLoader;
    }

    /**
     * Set the ClassLoader to use for bean classes.
     * <p>Default is {@code null}, which suggests to not load bean classes
     * eagerly but rather to just register bean definitions with class names,
     * with the corresponding Classes to be resolved later (or never).
     *
     * @see Thread#getContextClassLoader()
     */
    public void setBeanClassLoader(ClassLoader beanClassLoader) {
        this.beanClassLoader = beanClassLoader;
    }

    @Override
    public Environment getEnvironment() {
        return this.environment;
    }

    /**
     * Set the Environment to use when reading bean definitions. Most often used
     * for evaluating profile information to determine which bean definitions
     * should be read and which should be omitted.
     */
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public BeanNameGenerator getBeanNameGenerator() {
        return this.beanNameGenerator;
    }

    /**
     * Set the BeanNameGenerator to use for anonymous beans
     * (without explicit bean name specified).
     * <p>Default is a {@link DefaultBeanNameGenerator}.
     */
    public void setBeanNameGenerator(BeanNameGenerator beanNameGenerator) {
        this.beanNameGenerator = (beanNameGenerator != null ? beanNameGenerator : DefaultBeanNameGenerator.INSTANCE);
    }

    @Override
    public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
        int count = 0;
        for (Resource resource : resources) {
            count += loadBeanDefinitions(resource);
        }
        return count;
    }

    @Override
    public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
        return loadBeanDefinitions(location, null);
    }

    /**
     * Load bean definitions from the specified resource location.
     * <p>The location can also be a location pattern, provided that the
     * ResourceLoader of this bean definition reader is a ResourcePatternResolver.
     *
     * @param location        the resource location, to be loaded with the ResourceLoader
     *                        (or ResourcePatternResolver) of this bean definition reader
     * @param actualResources a Set to be filled with the actual Resource objects
     *                        that have been resolved during the loading process. May be {@code null}
     *                        to indicate that the caller is not interested in those Resource objects.
     * @return the number of bean definitions found
     * @throws BeanDefinitionStoreException in case of loading or parsing errors
     * @see #getResourceLoader()
     * @see #loadBeanDefinitions(org.springframework.core.io.Resource)
     * @see #loadBeanDefinitions(org.springframework.core.io.Resource[])
     */
    public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
        ResourceLoader resourceLoader = getResourceLoader();
        if (resourceLoader == null) {
            throw new BeanDefinitionStoreException(
                    "Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
        }

        if (resourceLoader instanceof ResourcePatternResolver) {
            // Resource pattern matching available.
            try {
                Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
                int count = loadBeanDefinitions(resources);
                if (actualResources != null) {
                    Collections.addAll(actualResources, resources);
                }
                if (logger.isTraceEnabled()) {
                    logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");
                }
                return count;
            } catch (IOException ex) {
                throw new BeanDefinitionStoreException(
                        "Could not resolve bean definition resource pattern [" + location + "]", ex);
            }
        } else {
            // Can only load single resources by absolute URL.
            Resource resource = resourceLoader.getResource(location);
            int count = loadBeanDefinitions(resource);
            if (actualResources != null) {
                actualResources.add(resource);
            }
            if (logger.isTraceEnabled()) {
                logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
            }
            return count;
        }
    }

    @Override
    public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
        int count = 0;
        for (String location : locations) {
            count += loadBeanDefinitions(location);
        }
        return count;
    }

}